home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1996 January: Mac OS SDK / Dev.CD Jan 96 SDK / Dev.CD Jan 96 SDK1.toast / Development Kits (Disc 1) / QuickDraw™ GX / Programming Stuff / Sample Code / Printing Samples / Printer Drivers… / Scanning Generic LaserWriter / Generic LaserWriter.c < prev    next >
Encoding:
C/C++ Source or Header  |  1995-04-10  |  29.1 KB  |  1,010 lines  |  [TEXT/MPS ]

  1. /*-----------------------------------------------------------------------------
  2.  
  3.     Generic LaserWriter.c
  4.     
  5.         This part of the driver demonstrates how to handle your own 'scan'
  6.         resource processing.
  7.         
  8.        12/18/93 - dmh - Removed IIg-dependencies for b3.
  9.         9/13/93 - dmh - Updated for the b2 seed.
  10.         9/14/93 - dmh - Disabled the PostScript log which was created in the
  11.                         Extensions folder.
  12.         3/22/94 - dmh - Balanced gxInitialize override with a gxShutDown one.
  13.         6/18/94 - dmh - Added 'scan' handling support.
  14.         7/04/94 - dmh - Changed 'scan' loading so that only one fetch is done
  15.                         for either pre-scan handle regardless of how many DTPs
  16.                         are printing, and how many gxPostScriptScanStatusText
  17.                         and gxPostScriptScanPrinterText messages are processed.
  18.                         Note that this code now uses both the driver's class
  19.                         and instance contexts to store data.
  20.         7/11/94 - dmh - Changed HandleScanning and the two GXScanXXXText
  21.                         routines so that if the text becomes empty after
  22.                         prescanning, we do not consider that a return to
  23.                         "normal" status and reset our alerts.
  24.         7/12/94 - dmh - Gack!  Various uninitialized variables initialized.
  25.  
  26.     © 1991-1994 Apple Computer Inc.
  27.     
  28. -----------------------------------------------------------------------------*/
  29. #include "Generic LaserWriter.h"
  30.  
  31. /*    ______________________________________________________________
  32.  
  33.     DriverOpenConnection -
  34.     
  35.     This routine is an override for gxOpenConnection.  It forwards
  36.     the message and sets up our global data for the 'scan'
  37.     handling code.
  38.     
  39.     WHY THIS IS IMPORTANT:
  40.     
  41.     The 'scan' handling code will rely on our global data for
  42.     status handling state information.
  43.  
  44.     ______________________________________________________________    */
  45.  
  46. OSErr DriverOpenConnection()
  47. {
  48.     OSErr    err;
  49.  
  50. /*
  51.     Forward the gxOpenConnection message, after setting up our class
  52.     context and instance context data.
  53.  
  54.     (We'll store most of our globals in the driver's instance
  55.     context, but we'll store the 'scan' handles in the driver's
  56.     class context.  This is because all instances of our driver
  57.     can share the same 'scan' handles, so we can avoid
  58.     duplication of data and wasted memory allocation.)
  59.     
  60.     If an error occurs, clean up after ourselves.
  61. */
  62.     err = SetUpClassContextData();
  63.     if (!err) err = SetUpInstanceContextData();
  64.  
  65.     if (err)
  66.     {
  67.         DisposeClassContextData();
  68.         DisposeInstanceContextData();
  69.     }
  70.     else
  71.     {
  72.         err = Forward_GXOpenConnection();
  73.         if (err) GXCleanupOpenConnection();
  74.     }
  75.  
  76.     return err;    
  77. }
  78.  
  79.  
  80. /*    ______________________________________________________________
  81.  
  82.     DriverCleanupOpenConnection -
  83.     
  84.     This routine is an override for gxCleanupOpenConnection.  It 
  85.     removes any data we allocated in DriverOpenConnection in the
  86.     event that an error occurs and DriverCloseConnection isn't
  87.     called.  It's important to do this if you allocate data in a
  88.     gxOpenConnection override and want to toss it when the
  89.     connection closes.
  90.     
  91.     WHY THIS IS IMPORTANT:
  92.     
  93.     gxCleanupOpenConnection will be sent instead of
  94.     gxCloseConnection if an error occurs after a successful open
  95.     connection attempt.
  96.  
  97.     ______________________________________________________________    */
  98.  
  99. void DriverCleanupOpenConnection()
  100. {
  101. /*
  102.     Forward the message, then dispose of our class and instance
  103.     context data.  (We will only decrement the owner count of the
  104.     class context data if we're not the last user.)
  105. */
  106.     Forward_GXCleanupOpenConnection();
  107.     DisposeClassContextData();
  108.     DisposeInstanceContextData();
  109. }
  110.  
  111.  
  112. /*    ______________________________________________________________
  113.  
  114.     DriverCloseConnection -
  115.     
  116.     This routine is an override for gxCloseConnection.  After
  117.     forwarding the message, it frees up our global data we stored
  118.     in DriverOpenConnection.
  119.     
  120.     WHY THIS IS IMPORTANT:
  121.     
  122.     You should always clean up after yourself, so nobody will
  123.     hurt themselves tripping over your junk.
  124.  
  125.     ______________________________________________________________    */
  126.  
  127. OSErr DriverCloseConnection()
  128. {
  129.     OSErr    err;
  130. /*
  131.     Forward the message, then dispose of our class and instance
  132.     context data.  (We will only decrement the owner count of the
  133.     class context data if we're not the last user.)
  134. */
  135.     err = Forward_GXCloseConnection();
  136.     DisposeClassContextData();
  137.     DisposeInstanceContextData();
  138.  
  139.     return err;
  140. }
  141.  
  142.  
  143. /*    ______________________________________________________________
  144.  
  145.     SetUpClassContextData -
  146.     
  147.     This routine sets up the data for our class context, if it
  148.     doesn't exist.  If it already exists, we simply increment
  149.     the owner count for our data.  Otherwise, we create a handle
  150.     for our data, set its owner count to 1, and store handles to
  151.     the 'scan' resources we'll use in ScanText.
  152.     
  153.     WHY THIS IS IMPORTANT:
  154.     
  155.     By caching these resources now, we don't have to waste time
  156.     disposing and reloading them each time we scan some text.
  157.     And, since we're sharing these handles between instances of
  158.     our driver, only one load of each resource will be performed,
  159.     regardless of how many desktop printers are printing at once.
  160.     These handles are eventually disposed of in the
  161.     DriverCloseConnection routine.
  162.  
  163.     ______________________________________________________________    */
  164.  
  165. OSErr SetUpClassContextData()
  166. {
  167.     OSErr                    err = noErr;
  168.     THz                        oldZone;
  169.     DriverClassGlobalsHdl    classGlobals;
  170.  
  171. /*
  172.     Get our current class context.  If non-nil, it's a handle to our
  173.     class context data.  In that case, bump the owner count.
  174.     Otherwise, the data hasn't been set up yet, so create a handle
  175.     for our class context data, and set the owner count to 1.
  176. */
  177.     classGlobals = (DriverClassGlobalsHdl) GetMessageHandlerClassContext();
  178.     nrequire_action(classGlobals, ClassContextExists, ++(*classGlobals)->ownerCount;);
  179.  
  180.     classGlobals = (DriverClassGlobalsHdl) TempNewHandle(sizeof(DriverClassGlobals), &err);
  181.     nrequire(err, MemoryError);
  182.  
  183.     (*classGlobals)->ownerCount = 1;
  184.  
  185. /*
  186.     Get the detached 'scan' resources for gxScanStatusText and
  187.     gxScanPrinterText.  Store these handles in our global data.
  188.     Note that if a resources isn't found, then nil will be
  189.     stored as its handle's value.
  190. */
  191.     Send_GXFetchTaggedDriverData(gxPostscriptScanningType,
  192.                                  kStatusTextScanResID,
  193.                                  &(*classGlobals)->statusScanHdl);
  194.  
  195.     Send_GXFetchTaggedDriverData(gxPostscriptScanningType,
  196.                                  kPrinterTextScanResID,
  197.                                  &(*classGlobals)->printerScanHdl);
  198.  
  199. /*
  200.     Because HandleScanning requires a locked scan handle, we move
  201.     the handles high and lock them here.  Note that you could
  202.     remove this dependency in HandleScanning and DecodeScanEntry
  203.     at the expense of code readability.  Also note that MoveHHi
  204.     expects you to set the current zone to the handle's zone
  205.     before calling it.
  206. */
  207.     oldZone = GetZone();
  208.  
  209.     if ((*classGlobals)->statusScanHdl)
  210.     {
  211.         SetZone(HandleZone((*classGlobals)->statusScanHdl));
  212.         MoveHHi((*classGlobals)->statusScanHdl);
  213.         HLock((*classGlobals)->statusScanHdl);
  214.     }
  215.  
  216.     if ((*classGlobals)->printerScanHdl)
  217.     {
  218.         SetZone(HandleZone((*classGlobals)->printerScanHdl));
  219.         MoveHHi((*classGlobals)->printerScanHdl);
  220.         HLock((*classGlobals)->printerScanHdl);
  221.     }
  222.  
  223.     SetZone(oldZone);
  224.  
  225.  
  226. // Finally, store our data handle in the driver's class context.
  227.  
  228.     SetMessageHandlerClassContext(classGlobals);
  229.  
  230. MemoryError:
  231. ClassContextExists:
  232.     return err;
  233. }
  234.  
  235.  
  236. /*    ______________________________________________________________
  237.  
  238.     DisposeClassContextData -
  239.     
  240.     This routine decrements the owner count of the data we stored
  241.     in our class context, anbd disposes of the data if the owner
  242.     count goes to zero.
  243.     
  244.     WHY THIS IS IMPORTANT:
  245.     
  246.     Since several instances of our driver may be using this class
  247.     context data, we must maintain an owner count for it.  When
  248.     all instances are through using it, we dispose of the data.
  249.     To be safe, you should always use an owner count for any data
  250.     you store in a class context.  As unlikely as it may seem,
  251.     there's always the possibility that more than one instance of
  252.     your handler can be accessing the class context data at a
  253.     time, and it would be bad if you disposed of data that
  254.     another instance is still using.
  255.  
  256.     ______________________________________________________________    */
  257.  
  258. void DisposeClassContextData()
  259. {
  260.     DriverClassGlobalsHdl    classGlobals;
  261.  
  262. /*
  263.     Get our class context data and decrement the owner count.  If
  264.     the new owner count is zero, dispose of the data we stored in
  265.     SetUpClassContextData.  Otherwise, one of our instanciations
  266.     is still using the data so leave it be.
  267. */
  268.     classGlobals = (DriverClassGlobalsHdl) GetMessageHandlerClassContext();
  269.     require(classGlobals, NoGlobalsSet);
  270.  
  271.     if (--(*classGlobals)->ownerCount == 0)
  272.     {
  273.         if ((*classGlobals)->statusScanHdl)
  274.             DisposHandle((*classGlobals)->statusScanHdl);
  275.         
  276.         if ((*classGlobals)->printerScanHdl)
  277.             DisposHandle((*classGlobals)->printerScanHdl);
  278.  
  279.         DisposHandle((Handle) classGlobals);
  280.         SetMessageHandlerClassContext(nil);
  281.     }
  282.  
  283. NoGlobalsSet:;
  284. }
  285.  
  286.  
  287. /*    ______________________________________________________________
  288.  
  289.     SetUpInstanceContextData -
  290.     
  291.     This routine fills in the handle we'll store in our class
  292.     context.  This involves setting up an owner count (initially
  293.     set to 1), and storing handles to the 'scan' resources we'll
  294.     use in ScanText.
  295.     
  296.     WHY THIS IS IMPORTANT:
  297.     
  298.     By caching these resources now, we don't have to waste time
  299.     disposing and reloading them each time we scan some text.
  300.     And, since we're sharing these handles between instances of
  301.     our driver, only one load of each resource will be performed,
  302.     regardless of how many desktop printers are printing at once.
  303.     These handles are eventually disposed of in the
  304.     DriverCloseConnection routine.
  305.  
  306.     ______________________________________________________________    */
  307.  
  308. OSErr SetUpInstanceContextData()
  309. {
  310.     OSErr                        err;
  311.     DriverInstanceGlobalsHdl    instanceGlobals;
  312.  
  313. // Create and store our instance context data.
  314.  
  315.     instanceGlobals = (DriverInstanceGlobalsHdl) TempNewHandle(sizeof(DriverInstanceGlobals), &err);
  316.     nrequire(err, Failed_NewGlobals);
  317.     SetMessageHandlerInstanceContext((void *) instanceGlobals);
  318.     
  319.     (*instanceGlobals)->printStatus =
  320.         (gxStatusRecord *) NewPtrSysClear(sizeof(gxStatusRecord) +gxDefaultStatusBufferSize -1L);
  321.  
  322.     nrequire((err = MemError()), Failed_NewStatusRec);
  323.     StoreInstanceContextStrings(instanceGlobals);
  324.  
  325. Failed_NewStatusRec:
  326. Failed_NewGlobals:
  327.     return err;
  328. }
  329.  
  330.  
  331. /*    ______________________________________________________________
  332.  
  333.     StoreInstanceContextStrings -
  334.     
  335.     This routine stores the current job's application, user,
  336.     document and output printer names in our instance globals.
  337.     
  338.     WHY THIS IS IMPORTANT:
  339.     
  340.     By caching these values at gxOpenConnection time, we don't
  341.     have to repeat this work in the 'scan' handling code.
  342.  
  343.     ______________________________________________________________    */
  344.  
  345. void StoreInstanceContextStrings(DriverInstanceGlobalsHdl instanceGlobals)
  346. {
  347.     OSErr        err;
  348.     long        itemSize;
  349.     gxJobInfo    jobInfo;
  350.     gxJob        currentJob = GXGetJob();
  351.  
  352. /*
  353.     Get the current job's application, user, document
  354.     and output printer names.
  355. */
  356.     itemSize = sizeof(gxJobInfo);
  357.     err = GetCollectionItem(GXGetJobCollection(currentJob),
  358.                             gxJobTag, gxPrintingTagID,
  359.                             &itemSize, &jobInfo);
  360.  
  361. /*
  362.     No job info?  Use empty strings.
  363.     Otherwise, move the strings to our instance data.
  364. */
  365.     if (err != noErr)
  366.     {
  367.         (*instanceGlobals)->userName[0] =
  368.         (*instanceGlobals)->appName[0] =
  369.         (*instanceGlobals)->documentName[0] = (char) 0;
  370.     }
  371.     else
  372.     {
  373.         BlockMove(jobInfo.userName, (*instanceGlobals)->userName, (long) jobInfo.userName[0] +1);
  374.         BlockMove(jobInfo.appName, (*instanceGlobals)->appName, (long) jobInfo.appName[0] +1);
  375.         BlockMove(jobInfo.documentName, (*instanceGlobals)->documentName, (long) jobInfo.documentName[0] +1);
  376.     }
  377.     
  378.     GXGetPrinterName(GXGetJobOutputPrinter(currentJob), (*instanceGlobals)->printerName);
  379. }
  380.  
  381.  
  382. /*    ______________________________________________________________
  383.  
  384.     DisposeInstanceContextData -
  385.     
  386.     This routine disposes of the data we stored in our instance
  387.     context.
  388.     
  389.     WHY THIS IS IMPORTANT:
  390.     
  391.     ditto.
  392.  
  393.     ______________________________________________________________    */
  394.  
  395. void DisposeInstanceContextData()
  396. {
  397.     DriverInstanceGlobalsHdl    instanceGlobals;
  398.  
  399. // Get our instance context data.
  400.  
  401.     instanceGlobals = (DriverInstanceGlobalsHdl) GetMessageHandlerInstanceContext();
  402.     require(instanceGlobals, NoGlobalsSet);
  403.  
  404. /*
  405.     Dispose of our status record, which we allocated in
  406.     SetUpInstanceContextData.  Then, dispose of our data handle and
  407.     set our instance context to nil, to "invalidate" it.
  408. */
  409.     if ((*instanceGlobals)->printStatus)
  410.         DisposePtr((Ptr) (*instanceGlobals)->printStatus);
  411.         
  412.     DisposHandle((Handle) instanceGlobals);
  413.     SetMessageHandlerInstanceContext(nil);
  414.  
  415. NoGlobalsSet:;
  416. }
  417.  
  418.  
  419. /*    ______________________________________________________________
  420.  
  421.     DriverPostScriptScanPrinterText -
  422.     
  423.     This routine is an override for gxPostScriptScanPrinterText.
  424.     By overriding this message, we can 'scan' the passed text
  425.     ourselves before (or instead of) passing the text to GX's
  426.     default implementation.
  427.     
  428.     WHY THIS IS IMPORTANT:
  429.     
  430.     This is how we will tap into the 'scan' mechanism and put up
  431.     alerts or desktop printer messages based on our own criteria.
  432.     That means that we aren't confined to only using the 'scan'
  433.     mechanism for handling the predefined GX error conditions. 
  434.  
  435.     ______________________________________________________________    */
  436.  
  437. OSErr DriverPostScriptScanPrinterText(Handle textHdl)
  438. {
  439.     OSErr        err;
  440.     Boolean        alertedUser;
  441.  
  442. // "Pre-scan" the text.
  443.  
  444.     err = ScanText(textHdl, kScanPrinterTextType, &alertedUser);
  445.  
  446. /*
  447.     Now do the "default" scanning, unless there's an error, we
  448.     called GXAlertTheUser to post a message, or there's no text
  449.     left to scan. This keeps our last alert or message displayed
  450.     as long as we want, without the default implementation
  451.     replacing it.
  452. */
  453.     if (!err && !alertedUser && (*(long *) *textHdl != 0))
  454.         err = Forward_GXPostScriptScanPrinterText(textHdl);
  455.  
  456.     return err;
  457. }
  458.  
  459.  
  460. /*    ______________________________________________________________
  461.  
  462.     DriverPostScriptScanStatusText -
  463.     
  464.     This routine is an override for gxPostScriptScanStatusText.
  465.     By overriding this message, we can 'scan' the passed text
  466.     ourselves before (or instead of) passing the text to GX's
  467.     default implementation.
  468.     
  469.     WHY THIS IS IMPORTANT:
  470.     
  471.     This is how we will tap into the 'scan' mechanism and put up
  472.     alerts or desktop printer messages based on our own criteria.
  473.     That means that we aren't confined to only using the 'scan'
  474.     mechanism for handling the predefined GX error conditions. 
  475.  
  476.     ______________________________________________________________    */
  477.  
  478. OSErr DriverPostScriptScanStatusText(Handle textHdl)
  479. {
  480.     OSErr        err;
  481.     Boolean        alertedUser;
  482.  
  483. // "Pre-scan" the text.
  484.  
  485.     err = ScanText(textHdl, kScanStatusTextType, &alertedUser);
  486.  
  487. /*
  488.     Now do the "default" scanning, unless there's an error, we
  489.     called GXAlertTheUser to post a message, or there's no text
  490.     left to scan. This keeps our last alert or message displayed
  491.     as long as we want, without the default implementation
  492.     replacing it.
  493. */
  494.     if (!err && !alertedUser && (*(long *) *textHdl != 0))
  495.         err = Forward_GXPostScriptScanStatusText(textHdl);
  496.  
  497.     return err;
  498. }
  499.  
  500.  
  501. /*    ______________________________________________________________
  502.  
  503.     ScanText -
  504.     
  505.     This routine is called to start the scanning process for the
  506.     text passed, using the 'scan' resource indicated.  On return,
  507.     alertedUser will be set to true if GXAlertTheUser has been
  508.     called to post a message.  This indicates that the relevant
  509.     GXPostScriptScanXXXText message should not be forwarded, or
  510.     the default implementation might replace our alert or message.
  511.     
  512.     WHY THIS IS IMPORTANT:
  513.     
  514.     This code calls HandleScanning, which applies our 'scan'
  515.     resource to the text handle we're working on.
  516.  
  517.     ______________________________________________________________    */
  518.  
  519. OSErr ScanText(Handle textHdl, char scanType, Boolean *alertedUser)
  520. {
  521.     OSErr                    err = noErr;
  522.     Handle                    scanHdl;
  523.     DriverClassGlobalsHdl    classDataHdl;
  524.  
  525. /*
  526.     Initially, assume that we won't alert the user.
  527.  
  528.     Get the driver's global data, then reference the
  529.     indicated 'scan' handle.
  530. */
  531.     *alertedUser = false;
  532.     classDataHdl = (DriverClassGlobalsHdl) GetMessageHandlerClassContext();
  533.     require(classDataHdl, NoGlobalsSet);
  534.  
  535.     scanHdl = (scanType == kScanStatusTextType)?
  536.                 (*classDataHdl)->statusScanHdl: (*classDataHdl)->printerScanHdl;
  537.  
  538.     require(scanHdl, ScanHandleIsNil);
  539.  
  540. // Apply the 'scan' resource to the text.
  541.  
  542.     err = HandleScanning(textHdl, scanHdl, alertedUser);
  543.  
  544. ScanHandleIsNil:
  545. NoGlobalsSet:
  546.     return err;
  547. }
  548.  
  549.  
  550. /*    ______________________________________________________________
  551.  
  552.     HandleScanning -
  553.     
  554.     This routine is the main controlling routine of the scanning
  555.     process.  On return, alertedUser will be set to true if
  556.     GXAlertTheUser has been called to post a message.  This
  557.     indicates that the relevant GXPostScriptScanXXXText message
  558.     should not be forwarded, or the default implementation might
  559.     replace our alert or message.
  560.     
  561.     WHY THIS IS IMPORTANT:
  562.     
  563.     This code applies the settings in a 'scan' resource to the
  564.     text passed.
  565.  
  566.     ______________________________________________________________    */
  567.  
  568. OSErr HandleScanning(Handle textHdl, Handle scanHdl, Boolean *alertedUser)
  569. {
  570.     OSErr                        err = noErr;
  571.     Boolean                        found;
  572.     Str255                        searchString, replaceString;
  573.     char                        *str1Ptr, *str2Ptr;
  574.     short                        *scanPtr, priorStatusLevel, priorStatusIndex;
  575.     short                        offsetType, actionType, statusIndex, statusLevel = Normal;
  576.     long                        endOfData, searchStringLen, replaceStringLen;
  577.     gxStatusRecord                *statusPtr;
  578.     DriverInstanceGlobalsHdl    instanceStateHdl;
  579.  
  580. /*
  581.     The first long in the textHdl is the text length.  If zero,
  582.     don't scan.  Otherwise, retrieve our driver's instance data,
  583.     the previous status level and the previous 'stat' index.
  584.     Next, reset the current status level to Normal.  We will
  585.     adjust this below, based on the result of our 'scan' handling.
  586. */
  587.     require(*(long *) *textHdl, NoTextToScan);
  588.  
  589.     instanceStateHdl = (DriverInstanceGlobalsHdl) GetMessageHandlerInstanceContext();
  590.     require(instanceStateHdl, NoGlobalsSet);
  591.     statusPtr = (*instanceStateHdl)->printStatus;
  592.     require(statusPtr, NoGlobalsSet);
  593.  
  594.     priorStatusLevel = (*instanceStateHdl)->statusLevel;
  595.     priorStatusIndex = (*instanceStateHdl)->statusIndex;
  596.     (*instanceStateHdl)->statusLevel = Normal;
  597.  
  598. /*
  599.     Calculate where the last byte of the 'scan' handle is,
  600.     and move past the owner count in our 'scan' handle.
  601. */
  602.     endOfData = (*scanHdl) +GetHandleSize(scanHdl);
  603.     scanPtr = (short *) (*scanHdl +4L);
  604.  
  605. /*
  606.     While there are more 'scan' entries to process, decode
  607.     them and pass the results to Munger.
  608. */
  609.     do
  610.     {
  611.         str1Ptr = searchString;
  612.         str2Ptr = replaceString;
  613.  
  614.         err = DecodeScanEntry(&scanPtr,
  615.                               &str1Ptr, &searchStringLen,
  616.                               &str2Ptr, &replaceStringLen,
  617.                               &offsetType, &actionType,
  618.                               &statusLevel, &statusIndex);
  619.  
  620.         nrequire(err, DecodeScanEntry_Failed);
  621.  
  622.         found = MungeTheText(offsetType, textHdl,
  623.                              str1Ptr, searchStringLen,
  624.                              str2Ptr, replaceStringLen);
  625.  
  626. /*
  627.     If Munger finds a match and the 'scan' entry's action
  628.     is SimpleAction, and the status level of this entry is
  629.     greater than the current level or this is exactly the
  630.     status we found the last time we scanned, update the
  631.     current status level and 'stat' index.  This way, we
  632.     only extract the most urgent SimpleAction for processing
  633.     below.
  634. */
  635.         if (found && (actionType == SimpleAction) &&
  636.             ((statusLevel > (*instanceStateHdl)->statusLevel) ||
  637.             ((statusLevel == priorStatusLevel) && (statusIndex == priorStatusIndex))))
  638.         {
  639.             (*instanceStateHdl)->statusLevel = statusLevel;
  640.             (*instanceStateHdl)->statusIndex = statusIndex;
  641.         }
  642.     }
  643.     while ((unsigned long) scanPtr < (unsigned long) endOfData);
  644.  
  645. /*
  646.     Display or clear any alerts and messages as necessary.
  647.     
  648.     NOTE:
  649.     If you use any alerts which require special handling (such as
  650.     aborting the print job if a cancel button is pressed) you'll
  651.     need to check the status record's dialogResult and do the
  652.     appropriate thing here.
  653. */
  654.     switch ((*instanceStateHdl)->statusLevel)
  655.     {
  656.         case Normal:                // Return status to normal.
  657.             if (priorStatusLevel == NonFatalError && (*(long *) *textHdl != 0))
  658.                 err = SetAlertStatus(kPrescanStatResID, kPrinterIsReadyIndex);
  659.             break;
  660.  
  661.                                     // Non fatal error.  If this is different
  662.                                     // than the currently posted non-fatal
  663.                                     // alert (if any), clear the previous alert
  664.                                     // before posting the new one.  Indicate that
  665.         case NonFatalError:            // we have posted a message with GXAlertTheUser.
  666.  
  667.             if ((priorStatusLevel == NonFatalError) &&
  668.                 (priorStatusIndex != (*instanceStateHdl)->statusIndex))
  669.                 err = SetAlertStatus(kPrescanStatResID, kPrinterIsReadyIndex);
  670.  
  671.             if (!err)
  672.                 err = SetAlertStatus(kPrescanStatResID, (*instanceStateHdl)->statusIndex);
  673.  
  674.             *alertedUser = true;
  675.             break;
  676.         
  677.         case FatalError:            // Fatal error.  Retrieve the error code.
  678.             err = (*instanceStateHdl)->statusIndex;
  679.             break;
  680.     }
  681.  
  682. DecodeScanEntry_Failed:
  683. NoGlobalsSet:
  684. NoTextToScan:
  685.     return err;
  686. }
  687.  
  688.  
  689. /*    ______________________________________________________________
  690.  
  691.     DecodeScanEntry -
  692.     
  693.     This routine parses one entry in a 'scan' resource and
  694.     determines what it all means.
  695.  
  696.     WHY THIS IS IMPORTANT:
  697.     
  698.     This is the code that turns 'scan' handle mush into coherant
  699.     data.
  700.  
  701.     ______________________________________________________________    */
  702.  
  703. OSErr DecodeScanEntry(short **scanPtrHolder,
  704.                       char **str1Ptr, long *str1Len,
  705.                       char **str2Ptr, long *str2Len,
  706.                       short *offsetType, short *actionType,
  707.                       short *statusLevel, short *statusIndex)
  708. {
  709.     OSErr                        err = noErr;
  710.     DriverInstanceGlobalsHdl    instanceStateHdl;
  711.     long                        *whichLen;
  712.     char                        parsePass, *whichString;
  713.     short                        stringType, *scanPtr = *scanPtrHolder;
  714.  
  715. // Retrieve our globals.
  716.  
  717.     instanceStateHdl = (DriverInstanceGlobalsHdl) GetMessageHandlerInstanceContext();
  718.  
  719.     for (parsePass = 0; parsePass < 2; parsePass++)
  720.     {
  721. /*
  722.     We make two passes through this loop, extracting the
  723.     search string entry on the first pass, and the replacement
  724.     string entry on the second.
  725. */
  726.         whichLen = (parsePass == 0)? str1Len: str2Len;
  727.         whichString = (parsePass == 0)? *str1Ptr: *str2Ptr;
  728.  
  729. // Get the entry's string type and move past the word it occupied.
  730.  
  731.         stringType = *scanPtr;
  732.         ++scanPtr;
  733.  
  734. // Now extract any auxiliary data for this string type.
  735.         
  736.         switch (stringType)
  737.         {
  738. /*
  739.     SimpleScan entries have a length word followed by
  740.     word-aligned string data.  For these, get the length,
  741.     move past that word, BlockMove the characters to our
  742.     string, and update our scanPtr to the word after the
  743.     last character (remember, it's word-aligned).
  744. */
  745.             case SimpleScan:
  746.                 *whichLen = *scanPtr;
  747.                 ++scanPtr;
  748.                 BlockMove(scanPtr, whichString, (long) *whichLen);
  749.                 scanPtr = (short *) (((char *) scanPtr) + *whichLen + (*whichLen & 1L));
  750.                 break;
  751.  
  752. /*
  753.     UserNameScan, DocumentNameScan, and PrinterNameScan
  754.     entries have no extra data, but we need to retrieve
  755.     the requested information.  We cached these strings
  756.     earlier, so we just need to store the lengths and
  757.     BlockMove the characters.  Note that there's no extra
  758.     data to move past, so we don't update the scanPtr.
  759. */
  760.             case UserNameScan:
  761.                 *whichLen = (*instanceStateHdl)->userName[0];
  762.                 BlockMove(&(*instanceStateHdl)->userName[1], whichString, *whichLen);
  763.                 break;
  764.  
  765.             case DocumentNameScan:
  766.                 *whichLen = (*instanceStateHdl)->documentName[0];
  767.                 BlockMove(&(*instanceStateHdl)->documentName[1], whichString, *whichLen);
  768.                 break;
  769.  
  770.             case PrinterNameScan:
  771.                 *whichLen = (*instanceStateHdl)->printerName[0];
  772.                 BlockMove(&(*instanceStateHdl)->printerName[1], whichString, *whichLen);
  773.                 break;
  774.  
  775. /*
  776.     NilPtrScan entries have a length word which indicates a
  777.     a length to pass to Munger along with the nil pointer.
  778.     For these, get the length, move past that word, and set
  779.     the appropriate string pointer to nil.
  780. */
  781.             case NilPtrScan:
  782.                 *whichLen = *scanPtr;
  783.                 ++scanPtr;
  784.  
  785.                 if (parsePass == 0)
  786.                     *str1Ptr = nil;
  787.                 else
  788.                     *str2Ptr = nil;
  789.  
  790.                 break;
  791.         }
  792.     }
  793.     
  794. //    Get the entry's offset and action types, and move past those words.
  795.     
  796.     *offsetType = *scanPtr;
  797.     ++scanPtr;
  798.     *actionType = *scanPtr;
  799.     ++scanPtr;
  800.     
  801. /*
  802.     If the action type is SimpleAction, get the status level,
  803.     (Normal, NonFatalError, or FatalError), move past that
  804.     word, get the 'stat' index to use, and move past that
  805.     word as well.
  806. */
  807.     if (*actionType == SimpleAction)
  808.     {
  809.         *statusLevel = *scanPtr;
  810.         ++scanPtr;
  811.         *statusIndex = *scanPtr;
  812.         ++scanPtr;
  813.     }
  814.     else
  815.         *statusLevel = *statusIndex = 0;
  816.  
  817. // Update the passed 'scan' pointer, and return any errors.
  818.  
  819.     *scanPtrHolder = scanPtr;
  820.     return err;
  821. }
  822.  
  823.  
  824. /*    ______________________________________________________________
  825.  
  826.     MungeTheText -
  827.     
  828.     This routine finds and replaces text in our text handle, based
  829.     on the results from our 'scan' parsing.  It also returns true
  830.     if the search string was found at least once.
  831.     
  832.     WHY THIS IS IMPORTANT:
  833.     
  834.     This code calls Munger to change the wording in our text
  835.     handle, which may end up in the desktop printer window.  It
  836.     also tells us if keywords we're watching for in our 'scan'
  837.     resource (like "cover open") are in the text handle.
  838.  
  839.     Briefly, text is munged as follows:
  840.     
  841.     SimpleOffset -            Find or Replace 1st occurance of the
  842.                             ptr1 string.
  843.  
  844.     SameAsPreviousOffset -    Insert the ptr2 text before the 1st
  845.                             occurance of the ptr1 string.
  846.  
  847.     ReturnedOffset -        Insert the ptr2 text after the first
  848.                             occurance of the ptr1 string.
  849.     SimpleRepeat,
  850.     SameAsPreviousRepeat,
  851.     ReturnedRepeat -        Same as the above methods but apply
  852.                             repeatedly to entire text handle.
  853.  
  854.     Now, here's what Munger will do with the data it's passed, in
  855.     the context of the above rules. (This Munger text comes from
  856.     Inside Mac.)
  857.  
  858.     •    If ptr1 is NIL, the substring of length len1 starting at
  859.         the given offset is replaced by the replacement string. If
  860.         len1 is negative, the substring from the given offset to
  861.         the end of the destination string is replaced by the
  862.         replacement string. In either case, Munger returns the
  863.         offset of the first byte past where the replacement
  864.         occurred.
  865.  
  866.     •    If len1 is 0, (ptr2,len2) is simply inserted at the given
  867.         offset; no text is replaced. Munger returns the offset of
  868.         the first byte past where the insertion occurred.
  869.  
  870.     •    If ptr2 is NIL, Munger returns the offset at which the
  871.         target string was found. The destination string isn’t
  872.         changed.
  873.  
  874.     •    If len2 is 0 (and ptr2 is not NIL), the target string is
  875.         deleted rather than replaced (since the replacement string
  876.         is empty). Munger returns the offset at which the deletion
  877.         occurred.
  878.  
  879.     ______________________________________________________________    */
  880.  
  881. Boolean    MungeTheText(short offsetType, Handle textHdl,
  882.                      void *ptr1, long len1,
  883.                      void *ptr2, long len2)
  884. {
  885.     long        handleSize, offset;
  886.     Boolean        found = false;
  887.  
  888. /*
  889.     The first longword of the text handle is the length of the
  890.     text in it.  Set the handle to that size (plus the length
  891.     longword).  Set our offset into the text handle to point
  892.     after the length longword.
  893. */
  894.     handleSize = *(long *) *textHdl;
  895.     SetHandleSize(textHdl, handleSize +4);
  896.     offset = 4;
  897.     if (handleSize <= 0) return false;
  898.  
  899.  
  900. /*
  901.     Based on the offsetType passed, do the appropriate munging.
  902.     See the description in the function header for more info.
  903. */
  904.     switch (offsetType)
  905.     {
  906.         case SimpleOffset:            // replace the old occurances.
  907.         case SimpleRepeat:
  908.             do
  909.             {
  910.                 offset = Munger(textHdl, offset, ptr1, len1, ptr2, len2);
  911.                 if (offset > 0) found = true;
  912.                 if (offset >= GetHandleSize(textHdl))
  913.                     offset = -1;
  914.             }
  915.             while ((offsetType == SimpleRepeat) && (offset > 0));
  916.             break;
  917.             
  918.         case SameAsPreviousOffset:    // insert just before the old occurances.
  919.         case SameAsPreviousRepeat:
  920.             do
  921.             {
  922.                 offset = Munger(textHdl, offset, ptr1, len1, nil, 0);
  923.                 if (offset > 0)
  924.                 {
  925.                     found = true;
  926.                     offset = Munger(textHdl, offset, nil, 0, ptr2, len2);
  927.                     offset += len1;
  928.                     if (offset >= GetHandleSize(textHdl))
  929.                         offset = -1;
  930.                 }
  931.             }
  932.             while ((offsetType == SameAsPreviousRepeat) && (offset > 0));
  933.             break;
  934.  
  935.         case ReturnedOffset:        // insert just after the old occurances.
  936.         case ReturnedRepeat:
  937.             do
  938.             {
  939.                 offset = Munger(textHdl, offset, ptr1, len1, nil, 0);
  940.                 if (offset > 0)
  941.                 {
  942.                     found = true;
  943.                     offset += len1;
  944.                     offset = Munger(textHdl, offset, nil, 0, ptr2, len2);
  945.                     if (offset > GetHandleSize(textHdl))
  946.                         offset = -1;
  947.                 }
  948.             }
  949.             while ((offsetType == ReturnedRepeat) && (offset > 0));
  950.             break;
  951.     }
  952.  
  953. /*
  954.     If we found a match, (and might have changed the text), reset
  955.     the text size longword in the text handle.
  956. */
  957.     if (found)
  958.         *(long *) *textHdl = GetHandleSize(textHdl) -4;
  959.  
  960.     return found;
  961. }
  962.  
  963.  
  964. /*    ______________________________________________________________
  965.  
  966.     SetAlertStatus -
  967.     
  968.     This routine calls GXAlertTheUser to display an alert, desktop
  969.     printer message, to clear the alert status (by passing the
  970.     printerReady code to GXAlertTheUser.
  971.  
  972.     WHY THIS IS IMPORTANT:
  973.     
  974.     This is the code that puts up and takes down dialogs, and
  975.     clears the printer status based on our 'scan' handling results.
  976.  
  977.     ______________________________________________________________    */
  978.  
  979. OSErr SetAlertStatus(short statResourceID, short statResourceIndex)
  980. {
  981.     OSErr                        err = noErr;
  982.     gxStatusRecord                 *statusPtr;
  983.     DriverInstanceGlobalsHdl    instanceStateHdl;
  984.  
  985. // Retrieve our global data.
  986.  
  987.     instanceStateHdl = (DriverInstanceGlobalsHdl) GetMessageHandlerInstanceContext();
  988.     require(instanceStateHdl, NoGlobalsSet);
  989.     statusPtr = (*instanceStateHdl)->printStatus;
  990.     require(statusPtr, NoGlobalsSet);
  991.  
  992. /*
  993.     Simply alert or update the dtp window based on what's
  994.     in 'stat' ID = statResourceID, index = statResourceIndex.
  995. */
  996.     statusPtr->statusType = 0;
  997.     statusPtr->statusId = 0;
  998.     statusPtr->statusAlertId = 0;
  999.     statusPtr->statusOwner = kCreator;
  1000.     statusPtr->statResId = statResourceID;
  1001.     statusPtr->statResIndex = statResourceIndex;
  1002.     statusPtr->dialogResult = 0;
  1003.     statusPtr->bufferLen = gxDefaultStatusBufferSize;
  1004.  
  1005.     err = GXAlertTheUser(statusPtr);
  1006.  
  1007. NoGlobalsSet:
  1008.     return err;
  1009. }
  1010.